﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Text.RegularExpressions;
using Shc.Json;

namespace Shc.Ajax
{
    /// <summary>
    /// 服务器端Ajax函数代理
    /// </summary>
    /// <param name="dataInput">客户端JSON数据输入</param>
    /// <param name="clientReturn">客户端状态返回(true表示成功, false表示失败, 默认true)</param>
    /// <param name="clientErrInfo">客户端当错误发生时返回信息</param>
    /// <returns></returns>
    public delegate JsonObject AjaxFunDelareHandler(JsonList dataInput, ref bool clientReturn, ref string clientErrInfo); 
    
    /// <summary>
    /// 服务器端Ajax验证函数代理
    /// </summary>
    /// <param name="context">当前网络上下文</param>
    /// <returns>bool</returns>
    public delegate bool AjaxFunValidateHandler(HttpContext context);

    internal delegate string MakeupAjaxRequestUrlHandler(string groupName, string funName);


    /// <summary>
    /// Ajax 函数结构
    /// </summary>
    class AjaxFunStruct  
    {
        public string FunName = "";
        public string FunParam = "";
        public AjaxFunDelareHandler ServerFun = null;
        public AjaxFunValidateHandler ValidateFun = null;

        public AjaxFunStruct(string funName, string funParam, AjaxFunDelareHandler serverFun, AjaxFunValidateHandler valFun)
        {
            this.FunName = funName;
            this.FunParam = funParam;
            this.ServerFun = serverFun;
            this.ValidateFun = valFun;
        }
    }

    /// <summary>
    /// Ajax 主类
    /// </summary>
    public class TinyAjax
    {
        private Dictionary<string, Dictionary<string, AjaxFunStruct>> g_GroupFunSets = 
            new Dictionary<string, Dictionary<string, AjaxFunStruct>>();

        private TinyAjax() {}

        public static TinyAjax Current
        {
            get
            {
                TinyAjax ins = (TinyAjax)HttpContext.Current.Application["__AJAX_CONTROLER__"];
                if (ins == null)
                {
                    ins = new TinyAjax();
                    HttpContext.Current.Application["__AJAX_CONTROLER__"] = ins;
                }
                return ins;
            }
        }

        /// <summary>
        /// 增加一个Ajax方法
        /// </summary>
        /// <param name="funGroupID">方法组ID, 客户端调用须包含funGroupID.cajax</param>
        /// <param name="jsFunName">客户端方法名</param>
        /// <param name="jsFunParam">方法参数</param>
        /// <param name="serverFun">服务器端方法代理</param>
        /// <param name="valFun">服务器端验证代理</param>
        public static void AddFun(string funGroupID, string jsFunName, string jsFunParam, AjaxFunDelareHandler serverFun, AjaxFunValidateHandler valFun)
        {
            Dictionary<string, AjaxFunStruct> funSets = null;
            if (Current.g_GroupFunSets.ContainsKey(funGroupID))
                funSets = Current.g_GroupFunSets[funGroupID];
            else
            {
                funSets = new Dictionary<string, AjaxFunStruct>();
                Current.g_GroupFunSets[funGroupID] = funSets;
            }

            if (!funSets.ContainsKey(jsFunName))
                funSets[jsFunName] = new AjaxFunStruct(jsFunName, jsFunParam, serverFun, valFun);
        }

        /// <summary>
        /// 增加一个Ajax方法
        /// </summary>
        /// <param name="funGroupID">方法组ID, 客户端调用须包含funGroupID.cajax</param>
        /// <param name="jsFunName">客户端方法名</param>
        /// <param name="jsFunParam">方法参数</param>
        /// <param name="serverFun">服务器端方法代理</param>
        public static void AddFun(string funGroupID, string jsFunName, string jsFunParam, AjaxFunDelareHandler serverFun)
        {
            AddFun(funGroupID, jsFunName, jsFunParam, serverFun, null);
        }

        internal static string OutPut(string funGroupID,  MakeupAjaxRequestUrlHandler makeUrlFun)
        {
            if (!Current.g_GroupFunSets.ContainsKey(funGroupID))
                return "";
            StringBuilder script = new StringBuilder();
            foreach (KeyValuePair<string, AjaxFunStruct> e in Current.g_GroupFunSets[funGroupID])
            {
                AjaxFunStruct curFun = e.Value;

                //define client fun
                script.Append("window." + curFun.FunName + " = function(" + curFun.FunParam);
                script.Append(string.IsNullOrEmpty(curFun.FunParam) ? "" : ", ");
                script.Append("onSuc, onFail, sucParam, sucCaller, isSync) {\r\n");
               
                //make up json
                string jsonScript = "";
                string[] funParams = curFun.FunParam.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach(string p in funParams)
                {
                    jsonScript += "," + p;
                }
                jsonScript = string.IsNullOrEmpty(jsonScript) ? "var json = []; " : ("var json = JSON.stringify([" + jsonScript.Substring(1) + "]);");
                script.Append("\t" + jsonScript + "\r\n");

                //load ajax
                script.Append("loadAjaxPostSync(isSync, '" + makeUrlFun(funGroupID, curFun.FunName) + "', escape(json), onSuc, onFail, sucParam, sucCaller);\r\n");

                //client fun end
                script.Append("\r\n};\r\n");
            }
            return script.ToString();
        }

        internal static AjaxFunStruct FindFun(string funGroupID, string funName)
        {
            if (!Current.g_GroupFunSets.ContainsKey(funGroupID))
                return null;
            Dictionary<string, AjaxFunStruct> funSets = Current.g_GroupFunSets[funGroupID];
            if (!funSets.ContainsKey(funName))
                return null;
            return funSets[funName];
        }

        /// <summary>
        /// Ajax请求验证
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        protected static bool AjaxRequestValidate(HttpContext context)
        {
            string val = context.Request.Headers["val-data"];
            string type = context.Request.Headers["content-type"];
            return !(string.IsNullOrEmpty(val) || string.IsNullOrEmpty(type) ||
                val.IndexOf("shc/tinyajax") == -1 || type.IndexOf("application/json") == -1);
        }

        /// <summary>
        /// 处理Ajax请求
        /// </summary>
        /// <param name="context"></param>
        /// <param name="groupName"></param>
        /// <param name="funName"></param>
        public static void AjaxRequestProcess(HttpContext context, string groupName, string funName)
        {
            AjaxFunStruct ServerFunStruct = null;
            JsonList jresList = new JsonList();
            if (!AjaxRequestValidate(context) || (ServerFunStruct = TinyAjax.FindFun(groupName, funName)) == null
                || (ServerFunStruct.ValidateFun != null && !ServerFunStruct.ValidateFun(context)))
            {
                context.Response.ClearContent();
                context.Response.ContentType = "text/plain";
                jresList.Add(false);
                jresList.Add((JsonObject)"403 Error.");
                context.Response.Write(JSON.Escape(JSON.JsonEncode(jresList)));
                return;
            }

            //read json
            byte[] buf = new byte[context.Request.ContentLength];
            context.Request.InputStream.Read(buf, 0, context.Request.ContentLength);
            string json = Shc.Json.JSON.UnEscape(Encoding.Default.GetString(buf));

            //call server fun
            bool serverFunReturn = true;
            string serverFunErrInfo = "";
            JsonObject jsonRes = ServerFunStruct.ServerFun(Shc.Json.JSON.JsonDecode(json), ref serverFunReturn, ref serverFunErrInfo);
            jresList.Add(serverFunReturn);
            jresList.Add(serverFunReturn ? jsonRes : (JsonObject)serverFunErrInfo);

            //back to client
            context.Response.ClearContent();
            context.Response.ContentType = "text/plain";
            context.Response.Write(JSON.Escape(JSON.JsonEncode(jresList)));
        }
    }

    /// <summary>
    /// *.ajax 处理器
    /// </summary>
    public class AjaxHttpHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            //get request path & val
            Regex reg = new Regex("(\\w+).(\\w+).ajax$");
            Match mres = reg.Match(context.Request.Path);
            if (!mres.Success)
            {
                context.Response.ClearContent();
                context.Response.ContentType = "text/plain";
                context.Response.StatusCode = 403;
                context.Response.End();
                return;
            }
            TinyAjax.AjaxRequestProcess(context, mres.Groups[1].Value, mres.Groups[2].Value);
        }
    }

    /// <summary>
    /// *.cajax 代理器
    /// </summary>
    public class CAjaxHttpHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get { return false; }
        }

        public string MakeAjaxRequestUrl(string group, string fun)
        {
            return group + "." + fun + ".ajax";
        }

        protected void OutputScript(HttpResponse response, string id)
        {
            response.ClearContent();
            response.ContentType = "application/x-javascript";
            response.Write(TinyAjax.OutPut(id, new MakeupAjaxRequestUrlHandler(MakeAjaxRequestUrl)));
        }

        public void ProcessRequest(HttpContext context)                
        {
            //get request path
            Regex reg = new Regex("(\\w+).cajax$");
            Match mres = reg.Match(context.Request.Path);
            string reqName = "";
            if (!mres.Success || string.IsNullOrEmpty((reqName = mres.Groups[1].Value)))
            {
                context.Response.Clear();
                context.Response.End();
                return;
            }
            OutputScript(context.Response, reqName);
        }
    }

    /// <summary>
    /// Aspx 处理器
    /// </summary>
    public static class AspxAjaxHandler
    {

        public static void ProcessRequest()
        {
            HttpContext context = HttpContext.Current;
            string ajax = context.Request.QueryString["ajax"];
            string cajax = context.Request.QueryString["cajax"];

            if (!string.IsNullOrEmpty(cajax))
                CAjaxProcessRequest(context, cajax);
            else if (!string.IsNullOrEmpty(ajax))
                AjaxProcessRequest(context, ajax);
            else
            {
                context.Response.ClearContent();
                context.Response.StatusCode = 500;
                context.Response.End();
            }
        }

        public static string makeUpAjaxRequestUrl(string group, string fun)
        {
            HttpContext context = HttpContext.Current;
            string ajax = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(group + "." + fun));
            return context.Request.Path.Substring(0, context.Request.Path.LastIndexOf(".aspx")) + ".aspx?ajax=" + ajax;
        }

        static void CAjaxProcessRequest(HttpContext context, string cajaxInfo)
        {
            context.Response.ClearContent();
            context.Response.ContentType = "application/x-javascript";
            context.Response.Write(TinyAjax.OutPut(cajaxInfo, new MakeupAjaxRequestUrlHandler(makeUpAjaxRequestUrl)));
        }


        static void AjaxProcessRequest(HttpContext context, string ajaxInfo)
        {
            ajaxInfo = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(ajaxInfo));
            Regex reg = new Regex("^(\\w+).(\\w+)$");
            Match mres = reg.Match(ajaxInfo);
            if (!mres.Success)
            {
                context.Response.ClearContent();
                context.Response.ContentType = "text/plain";
                context.Response.StatusCode = 403;
                context.Response.End();
                return;
            }
            TinyAjax.AjaxRequestProcess(context, mres.Groups[1].Value, mres.Groups[2].Value);
        }
    }
}
